package pt.iscte.iul.dcti.pa.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class OracleSQLGenerator implements SQLGenerator {

	@Override
	public List<SQLCommand> createTables(Class<?> c) {
		List<SQLCommand> commands = new ArrayList<SQLCommand>();
		StringBuilder sql = new StringBuilder("CREATE TABLE ").append(c.getSimpleName());
		
		Field primary = primaryKeyField(c);
		List<Field> fields = filterValueTypes(c);
		String[] cols = new String[fields.size()];
		
		int i = 0;
		for(Field f : fields) {
			if(primary != null && primary.equals(f))
				cols[i++] = primaryKeyColumn(f.getName(), f.getType());
			else
				cols[i++] = column(f.getName(), f.getType());
		}
		
		addColumns(sql, cols);
		
		SimpleSQLCommand cmd = new SimpleSQLCommand(sql.toString());
		commands.add(cmd);
		
		handleRelations(c, commands);
		
		return commands;
	}

	private String column(String name, Class<?> type) {
		return name + ' ' + mapType(type);
	}
	
	private String primaryKeyColumn(String name, Class<?> type) {
		return column(name, type) + " AS PRIMARY KEY";
	}
	
	

	private static void addColumns(StringBuilder text, String ...args) {
		text.append('(');
		for(int i = 0; i < args.length; i++) {
			if(i != 0)
				text.append(", ");
			
			text.append(args[i]);
		}
		text.append(')');
	}
	
	
	private void handleRelations(Class<?> c, List<SQLCommand> commands) {
		Field[] fields = c.getDeclaredFields();
		for(int i = 0; i < fields.length; i++) {
			Class<?> type = fields[i].getType();
			if(Collection.class.isAssignableFrom(type)) {
				Class<?> ref = getCollectionTypeArg(fields[i]);
				
				StringBuilder sql = new StringBuilder("CREATE TABLE ");
				sql.append(c.getSimpleName()).append("_").append(ref.getSimpleName());
				
				Field keyOwner = primaryKeyField(c);
				Field keyRef = primaryKeyField(ref);
				
				addColumns(sql, 
						column(keyOwner.getName(), keyOwner.getType()),
						column(keyRef.getName(), keyRef.getType()));

				commands.add(new SimpleSQLCommand(sql.toString()));
			}
		}
		
	}

	/**
	 * Extrai o tipo utilizado para parameterizar uma Collection
	 * e.g. para um campo do tipo List<String> sera devolvido String.class
	 */
	private static Class<?> getCollectionTypeArg(Field f) {
		if(!Collection.class.isAssignableFrom(f.getType()))
			throw new RuntimeException("Not a collection");
		
		Type type = f.getGenericType();
       
		if (!(type instanceof ParameterizedType))  
			throw new RuntimeException("Not parameterized type");
        
		ParameterizedType pt = (ParameterizedType) type;  
        Type[] typeArgs = pt.getActualTypeArguments();
        
        return (Class<?>) typeArgs[0];
	}
	
	private String mapType(Class<?> type) {
		if(type.equals(String.class))
			return "VARCHAR";
		else if(type.equals(int.class))
			return "NUMBER";
		
		throw new RuntimeException("Type not found");
	}

	private Field primaryKeyField(Class<?> c) {
		Field[] fields = c.getDeclaredFields();
		for(int i = 0; i < fields.length; i++) {
			if(fields[i].getName().endsWith("_ID"))
				return fields[i];
		}
		return null;
	}
	
	private List<Field> filterValueTypes(Class<?> c) {
		List<Field> list = new ArrayList<Field>();
		Field[] fields = c.getDeclaredFields();
		for(int i = 0; i < fields.length; i++) {
			Class<?> type = fields[i].getType();
			if(type.isPrimitive() || type.equals(String.class))
				list.add(fields[i]);
			
		}
		return list;
	}

}
